Skip to content

Conversation

@Pouyanpi
Copy link
Collaborator

@Pouyanpi Pouyanpi commented Nov 6, 2025

Description

Added support for extracting reasoning traces and tool calls from LangChain 1.x content blocks format, with automatic fallback to the legacy format for backward compatibility.

The implementation:

  • extracts reasoning from content_blocks with type: "reasoning"
  • extracts tool calls from content_blocks with type: "tool_call"
  • falls back to additional_kwargs and tool_calls attribute for providers using the legacy format

references:

requires:

#1472

Migrate all imports from deprecated proxy paths to canonical
langchain-core paths
to ensure compatibility with LangChain 1.x. Changes include:
- Use `from langchain_core.language_models import BaseLLM,
BaseChatModel`
- Remove proxy imports from `langchain.chat_models.base`
- Standardize submodule imports to top-level
langchain_core.language_models

This ensures forward compatibility with LangChain 1.x where proxy
imports from the main langchain package will be removed.
…patcher

Remove support for registering LangChain Chain objects as actions in
favor of
the modern Runnable interface. Chain support is deprecated in LangChain
1.x
and users should migrate to using Runnable objects instead.

- Remove Chain handling logic from action_dispatcher.py
- Remove Chain-based tests from test_runnable_rails.py
- Add deprecation warning in python-api.md documentation
Remove the built-in SummarizeDocument action which relied on deprecated
LangChain Chain features. Users who need document summarization should
implement custom actions using LangChain Runnable chains.

- Delete nemoguardrails/actions/summarize_document.py
- Remove related import from llm/filters.py
Add try/except fallback patterns in examples to support both LangChain
0.x and 1.x. When using LangChain 1.x, legacy Chain features are
imported from langchain-classic package with helpful error messages.

This allows examples to work seamlessly across LangChain versions
without requiring code changes from users.

- Add fallback imports for RetrievalQA, embeddings, text splitters,
vectorstores
- Provide clear error messages directing users to install
langchain-classic
Update Colang runtime imports to use canonical langchain-core paths for
callbacks and runnables. Part of the broader migration to langchain-core
for LangChain 1.x compatibility.
… support

Complete rewrite of the custom LLM provider documentation with:
- Separate comprehensive guides for BaseLLM (text completion) and
BaseChatModel (chat)
- Correct method signatures (_call vs _generate)
- Proper async implementations
- Clear registration instructions (register_llm_provider vs
register_chat_provider)
- Working code examples with correct langchain-core imports
- Important notes on choosing the right base class

This addresses the gap where users were not properly guided on
implementing
custom chat models and were being directed to the wrong interface.
Extend LangChain dependency constraints to support both 0.x and 1.x versions:
- langchain: >=0.2.14,<0.4.0 → >=0.2.14,<2.0.0
- langchain-core: >=0.2.14,<0.4.0 → >=0.2.14,<2.0.0
- langchain-community: >=0.2.5,<0.4.0 → >=0.2.5,<2.0.0

Remove langchain-nvidia-ai-endpoints from optional dependencies as it
should be installed manually when needed.

fix
@Pouyanpi Pouyanpi added this to the v0.19.0 milestone Nov 6, 2025
@Pouyanpi Pouyanpi self-assigned this Nov 6, 2025
@Pouyanpi Pouyanpi marked this pull request as ready for review November 6, 2025 14:49
@codecov
Copy link

codecov bot commented Nov 6, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

Added support for extracting reasoning traces and tool calls from LangChain 1.x content_blocks format, with automatic fallback to legacy additional_kwargs and tool_calls attribute for backward compatibility.

Key changes:

  • Refactored _store_reasoning_traces() to try content_blocks first, then fall back to additional_kwargs
  • Refactored _store_tool_calls() to try content_blocks first, then fall back to tool_calls attribute
  • Split extraction logic into separate helper functions for better modularity
  • Added comprehensive test coverage with 28 new tests covering edge cases and fallback behavior

Testing:

  • Tests cover both extraction methods, fallback behavior, multiple content blocks, and edge cases
  • Tests verify proper prioritization (content_blocks over legacy format)
  • Tests include both MockResponse and real LangChain AIMessage objects

Confidence Score: 4/5

  • This PR is safe to merge with minor risk from a KeyError edge case
  • Score reflects solid implementation with comprehensive tests and backward compatibility, but deducted one point for the KeyError risk in reasoning extraction when blocks have type "reasoning" but missing the key
  • Pay close attention to nemoguardrails/actions/llm/utils.py line 252 for the KeyError issue

Important Files Changed

File Analysis

Filename Score Overview
nemoguardrails/actions/llm/utils.py 4/5 Added LangChain 1.x content_blocks support with fallback to legacy format. Implementation is solid but has one KeyError risk in reasoning extraction.
tests/test_actions_llm_utils.py 5/5 Comprehensive test coverage for both extraction methods, fallback behavior, and edge cases with MockResponse and real AIMessage objects.

Sequence Diagram

sequenceDiagram
    participant LLM as LLM Call
    participant Store as _store_reasoning_traces/_store_tool_calls
    participant ExtractCB as _extract_from_content_blocks
    participant ExtractLegacy as _extract_from_legacy
    participant CtxVar as Context Variable

    LLM->>Store: response object
    Store->>ExtractCB: try content_blocks first
    alt content_blocks exists
        ExtractCB->>ExtractCB: iterate blocks
        ExtractCB->>ExtractCB: filter by type
        ExtractCB-->>Store: return extracted data
        Store->>CtxVar: set value
    else no content_blocks
        ExtractCB-->>Store: return None
        Store->>ExtractLegacy: fallback to legacy format
        alt legacy format exists
            ExtractLegacy-->>Store: return extracted data
            Store->>CtxVar: set value
        else no legacy format
            ExtractLegacy-->>Store: return None
            Store->>CtxVar: set None or original value
        end
    end
Loading

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Signed-off-by: Pouyan <[email protected]>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

Refactored LLM response processing to support LangChain 1.x content blocks format while maintaining backward compatibility with the legacy format.

Key Changes:

  • Extracted reasoning traces from content_blocks with type: "reasoning" (LangChain 1.x standard)
  • Extracted tool calls from content_blocks with type: "tool_call" (LangChain 1.x standard)
  • Implemented automatic fallback to additional_kwargs and tool_calls attribute for providers using legacy format
  • Refactored extraction logic into separate functions for better modularity and testability
  • Added comprehensive test coverage for both new content blocks format and legacy format

Architecture:
The implementation follows a try-first-then-fallback pattern: it first attempts to extract data from the newer content_blocks format, and only if that fails, falls back to the legacy format. This ensures compatibility with both old and new LangChain versions.

Confidence Score: 4/5

  • Safe to merge with one minor defensive check already flagged in previous comments
  • The refactoring is well-structured with good separation of concerns and comprehensive test coverage. The fallback mechanism ensures backward compatibility. One minor issue (missing key existence check in line 252) was already identified in previous comments and should be addressed before merging.
  • No files require special attention beyond addressing the previously flagged issue in nemoguardrails/actions/llm/utils.py:252

Important Files Changed

File Analysis

Filename Score Overview
nemoguardrails/actions/llm/utils.py 4/5 Refactored reasoning and tool call extraction to support LangChain 1.x content blocks with fallback to legacy format. Added proper defensive checks. One minor issue already flagged in previous comments.

Sequence Diagram

sequenceDiagram
    participant LLM as LLM Call
    participant Store as _store_reasoning_traces / _store_tool_calls
    participant CB as Content Blocks Extractor
    participant AK as Additional Kwargs Extractor
    participant CV as Context Variable

    LLM->>Store: response object
    
    alt Reasoning Extraction
        Store->>CB: _extract_reasoning_from_content_blocks
        CB->>CB: Check hasattr(response, "content_blocks")
        alt content_blocks exists
            CB->>CB: Iterate blocks
            CB->>CB: Find type="reasoning" with "reasoning" key
            CB-->>Store: Return reasoning string or None
        else no content_blocks
            CB-->>Store: Return None
        end
        
        alt No reasoning from content_blocks
            Store->>AK: _extract_reasoning_from_additional_kwargs
            AK->>AK: Check hasattr(response, "additional_kwargs")
            AK->>AK: Get "reasoning_content" from dict
            AK-->>Store: Return reasoning or None
        end
        
        Store->>CV: reasoning_trace_var.set(reasoning_content)
    end
    
    alt Tool Calls Extraction
        Store->>CB: _extract_tool_calls_from_content_blocks
        CB->>CB: Check hasattr(response, "content_blocks")
        alt content_blocks exists
            CB->>CB: Collect all type="tool_call" blocks
            CB-->>Store: Return list or None
        else no content_blocks
            CB-->>Store: Return None
        end
        
        alt No tool_calls from content_blocks
            Store->>AK: _extract_tool_calls_from_attribute
            AK->>AK: getattr(response, "tool_calls", None)
            AK-->>Store: Return tool_calls or None
        end
        
        Store->>CV: tool_calls_var.set(tool_calls)
    end
Loading

1 file reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Collaborator

@tgasser-nv tgasser-nv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, please address the comments on the tests before merging. These auto-generated tests tend to bloat the line-count of tests by checking individual contents of dicts.

These tests basically store and get the same data back in each test via different permutations. Recommend re-writing test to define a dict which is both stored and checked in the assert


def test_extract_tool_calls_from_content_blocks_single_tool_call():
response = MockResponse(
content_blocks=[
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Pull out the dict into a single variable and put it into the content_blocks and assert it matches below?



def test_store_tool_calls_from_content_blocks():
tool_calls_var.set(None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are ~13 tests which use the tool_calls_var and set it to None at the start and end of the tests. Consider using a pytest fixture like this:

@pytest.fixture(autouse=True)
def reset_tool_calls_var():
    """
    Automatically reset tool_calls_var before and after every test.
    """
    # token has the previous state
    token = tool_calls_var.set(None)
    
    # This is where the test itself will run
    yield
    
    # We use the token to reset the var to its previous state
    tool_calls_var.reset(token)

@Pouyanpi Pouyanpi force-pushed the feat/support-langchain-v1 branch from b040aaf to 5deb278 Compare November 17, 2025 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants